package org.tool.io;

import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.ValidateObjectInputStream;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.tool.io.file.FileUtil;
import org.tool.reflect.ClassLoaderUtil;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 输入输出流工具类
 *
 * @author 李坤
 * @date 2022/4/16 11:27
 */
public abstract class IOUtil {

    /**
     * 默认输出流构建大小 1024
     */
    public static final int DEFAULT_OUTPUT_STREAM_SIZE = 2 << 10;


    /**
     * 读取给定文件的数据，并将其转换为指定的格式
     *
     * @param file 带读取的文件
     *
     * @return 文件结果
     *
     * @throws IOException IO异常
     */
    public static Map<File, String> read(File file, Charset charset, Collection<String> ignore) throws IOException {
        final Map<File, String> mapping = new HashMap<>();
        for (File innerFile : FileUtil.getFileList(file, ignore)) {
            mapping.put(innerFile, readStr(Files.newInputStream(innerFile.toPath()), charset));
        }
        return mapping;
    }

    /**
     * 读取给定文件的数据，并将其转换为UTF-8格式
     *
     * @param file 带读取的文件
     *
     * @return 文件结果
     *
     * @throws IOException IO异常
     */
    public static Map<File, String> readUtf8(File file, Collection<String> ignore) throws IOException {
        return read(file, StandardCharsets.UTF_8, ignore);
    }

    /**
     * 读取给定resource文件下的数据
     *
     * @param path 相对于 resource（资源）文件的相对路径
     *
     * @return 读取的结果
     *
     * @throws IOException IO异常
     */
    public static String readClassPath2Utf8(String path) throws IOException {
        final ClassLoader classLoader = ClassLoaderUtil.getDefaultClassLoader();
        return readStrUtf8(classLoader.getResource(path).openStream());
    }

    /**
     * 读取给定的输入流，并将其转换为字符串
     *
     * @param inputStream 输入流
     *
     * @return 字符串
     *
     * @throws IOException IO异常
     */
    public static String readStr(InputStream inputStream, Charset charset) throws IOException {
        byte[] bytes = IOUtil.readInputStream2Byte(inputStream);
        return ObjectUtil.isNotEmpty(bytes) ? new String(bytes, charset) : CharSequenceUtil.EMPTY;
    }

    /**
     * 读取给定的输入流，并将其转换为字符串
     *
     * @param inputStream 输入流
     *
     * @return 字符串
     *
     * @throws IOException IO异常
     */
    public static String readStrUtf8(InputStream inputStream) throws IOException {
        return readStr(inputStream, StandardCharsets.UTF_8);
    }

    /**
     * 从给定的输入流中反序列化对象
     *
     * @param inputStream 输入流
     *
     * @return 序列化后的对象
     *
     * @throws IOException            IO异常
     * @throws ClassNotFoundException 类型转换异常
     */
    public static Object readObject(InputStream inputStream) throws IOException, ClassNotFoundException {
        return IOUtil.readObject(inputStream, Object.class);
    }

    /**
     * 从给定的输入流中反序列化为给定类型的对象
     *
     * @param inputStream 输入流
     * @param clazz       反序列化对象类型
     * @param <T>         反序列化对象类型
     *
     * @return 反序列化后的对象
     *
     * @throws IOException            IO异常
     * @throws ClassNotFoundException 类型转换异常
     */
    public static <T> T readObject(InputStream inputStream, Class<T> clazz) throws IOException, ClassNotFoundException {
        try (ValidateObjectInputStream stream = new ValidateObjectInputStream(inputStream, clazz)) {
            return (T) stream.readObject();
        }
    }


    /**
     * 将给定的数据转换为字符串，并输出到给定的输出流中
     *
     * @param outputStream 输出流
     * @param data2Written 输出数据
     *
     * @throws IOException IO异常
     */
    public static void writeUtf8(OutputStream outputStream, Object... data2Written) throws IOException {
        try (OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) {
            for (Object target : data2Written) {
                writer.write(StrUtil.toString(target));
            }
            writer.flush();
        }
    }

    /**
     * 将给定的对象输出到给定的输出流中
     *
     * @param outputStream 输出流
     * @param data2Written 输出数据
     *
     * @throws IOException IO异常
     */
    public static void writeObject(OutputStream outputStream, Object... data2Written) throws IOException {
        try (ObjectOutputStream output = new ObjectOutputStream(outputStream)) {
            for (Object target : data2Written) {
                output.writeObject(target);
            }
            output.flush();
        }
    }

    /**
     * 将给定的输入流转换为 byte[] 对象
     *
     * @param inputStream 输入流
     *
     * @return byte[] 对象
     *
     * @throws IOException IO异常
     */
    public static byte[] readInputStream2Byte(InputStream inputStream) throws IOException {
        if (inputStream instanceof FileInputStream) {
            final byte[] result;
            // 文件流的长度是可预见的，此时直接读取效率更高，不会发生阻塞
            try (FileInputStream fileInputStream = (FileInputStream) inputStream) {
                result = new byte[inputStream.available()];
                inputStream.read(result);
            }
            return result;
        }
        return IOUtil.generateFastByteArrayOutputStream(inputStream).toByteArray();
    }

    /**
     * 将给定的输入流转换为 FastByteArrayOutputStream 对象
     *
     * @param inputStream 输入流
     *
     * @return FastByteArrayOutputStream 对象
     *
     * @throws IOException IO异常
     */
    public static FastByteArrayOutputStream generateFastByteArrayOutputStream(InputStream inputStream) throws IOException {
        // 文件流直接读取效率更高
        int available = (inputStream instanceof FileInputStream) ? inputStream.available() : DEFAULT_OUTPUT_STREAM_SIZE;

        try (FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream(available)) {
            IOUtil.copyStream(inputStream, outputStream);
            return outputStream;
        }
    }

    /**
     * 将给定的输入流数据复制到给定的输出流中
     *
     * @param inputStream  输入流
     * @param outputStream 输出流
     *
     * @throws IOException IO异常
     */
    public static void copyStream(InputStream inputStream, OutputStream outputStream) throws IOException {
        IOUtil.copyStream(inputStream, outputStream, true);
    }

    /**
     * 将给定的输入流数据复制到给定的输出流中
     *
     * @param inputStream      输入流
     * @param outputStream     输出流
     * @param closeInputStream 是否关闭输入流
     *
     * @throws IOException IO异常
     */
    public static void copyStream(InputStream inputStream, OutputStream outputStream, boolean closeInputStream) throws IOException {
        int length;
        final byte[] bytes = new byte[DEFAULT_OUTPUT_STREAM_SIZE];

        while (IOUtil.isNotOver(length = inputStream.read(bytes))) {
            outputStream.write(bytes, 0, length);
            outputStream.flush();
        }
        // 此处将关闭输入流
        if (closeInputStream) {
            inputStream.close();
        }
    }


    /*------------------------------------------------private methods-------------------------------------------------*/


    /**
     * 判断给定的输入流是否已经读取完毕
     *
     * @param index 读取的字节数
     *
     * @return 是否已经读取完毕
     */
    private static boolean isNotOver(int index) {
        return index != -1;
    }

}
